﻿#include "souistd.h"
#include "helper/SDIBHelper.h"

#define RGB2GRAY(r, g, b) (((b)*117 + (g)*601 + (r)*306) >> 10)

SNSBEGIN
struct DIBINFO
{
    LPBYTE pBits;
    UINT nWid;
    UINT nHei;
};

// ------------------------------------------------------------
// 有太多的算法需要用某种方式(map)变换位图的每个像素的颜色，比如
// 彩色转换为灰度图，gamma校正，颜色空间转换,hsl调整.所以写一个模板做为参数调用的通用算法
// ------------------------------------------------------------
template <class Mode, class Param>
bool ColorTransform(DIBINFO *pDib, Mode mode, const Param &param)
{
    if (NULL == pDib || NULL == pDib->pBits)
    {
        return false;
    }

    SMap<DWORD, DWORD> cache;
    int nPixels = pDib->nWid * pDib->nHei;
    LPBYTE pBit = pDib->pBits;
    for (int i = 0; i < nPixels; i++, pBit += 4)
    {
        DWORD crFrom = *(DWORD *)pBit;
        SMap<DWORD, DWORD>::CPair *p = cache.Lookup(crFrom);
        if (p)
        {
            memcpy(pBit, &p->m_value, 4);
        }
        else
        {
            mode(pBit, param);
            cache[crFrom] = *(DWORD *)pBit;
        }
    }

    return true;
}

// 灰度 = 0.299 * red + 0.587 * green + 0.114 * blue
static void GrayMode(BYTE *pColor, const int &)
{
    pColor[0] = pColor[1] = pColor[2] = RGB2GRAY(pColor[0], pColor[1], pColor[2]);
}

struct COLORIZEPARAM
{
    BYTE hue;
    BYTE sat;
    int a0; //[0-256]
    int a1; //[0-256]
};

static void FillColorizeParam(COLORIZEPARAM &param, BYTE hue, BYTE sat, float fBlend)
{
    param.hue = hue;
    param.sat = sat;
    SASSERT(fBlend >= 0.0f && fBlend <= 1.0f);
    param.a0 = (int)(fBlend * 256);
    param.a1 = 256 - param.a0;
}

////////////////////////////////////////////////////////////////////////////////
#define HSLMAX 255 /* H,L, and S vary over 0-HSLMAX */
#define RGBMAX 255 /* R,G, and B vary over 0-RGBMAX */
/* HSLMAX BEST IF DIVISIBLE BY 6 */
/* RGBMAX, HSLMAX must each fit in a BYTE. */
/* Hue is undefined if Saturation is 0 (grey-scale) */
/* This value determines where the Hue scrollbar is */
/* initially set for achromatic colors */
#define HSLUNDEFINED (HSLMAX * 2 / 3)
////////////////////////////////////////////////////////////////////////////////
static RGBQUAD RGBtoHSL(RGBQUAD lRGBColor)
{
    BYTE R, G, B;                /* input RGB values */
    BYTE H, L, S;                /* output HSL values */
    BYTE cMax, cMin;             /* max and min RGB values */
    WORD Rdelta, Gdelta, Bdelta; /* intermediate value: % of spread from max*/

    R = lRGBColor.rgbRed; /* get R, G, and B out of DWORD */
    G = lRGBColor.rgbGreen;
    B = lRGBColor.rgbBlue;

    cMax = smax(smax(R, G), B); /* calculate lightness */
    cMin = smin(smin(R, G), B);
    L = (BYTE)((((cMax + cMin) * HSLMAX) + RGBMAX) / (2 * RGBMAX));

    if (cMax == cMin)
    {                     /* r=g=b --> achromatic case */
        S = 0;            /* saturation */
        H = HSLUNDEFINED; /* hue */
    }
    else
    {                          /* chromatic case */
        if (L <= (HSLMAX / 2)) /* saturation */
            S = (BYTE)((((cMax - cMin) * HSLMAX) + ((cMax + cMin) / 2)) / (cMax + cMin));
        else
            S = (BYTE)((((cMax - cMin) * HSLMAX) + ((2 * RGBMAX - cMax - cMin) / 2)) / (2 * RGBMAX - cMax - cMin));
        /* hue */
        Rdelta = (WORD)((((cMax - R) * (HSLMAX / 6)) + ((cMax - cMin) / 2)) / (cMax - cMin));
        Gdelta = (WORD)((((cMax - G) * (HSLMAX / 6)) + ((cMax - cMin) / 2)) / (cMax - cMin));
        Bdelta = (WORD)((((cMax - B) * (HSLMAX / 6)) + ((cMax - cMin) / 2)) / (cMax - cMin));

        if (R == cMax)
            H = (BYTE)(Bdelta - Gdelta);
        else if (G == cMax)
            H = (BYTE)((HSLMAX / 3) + Rdelta - Bdelta);
        else /* B == cMax */
            H = (BYTE)(((2 * HSLMAX) / 3) + Gdelta - Rdelta);

        //		if (H < 0) H += HSLMAX;     //always false
        if (H > HSLMAX)
            H -= HSLMAX;
    }
    RGBQUAD hsl = { L, S, H, 0 };
    return hsl;
}

////////////////////////////////////////////////////////////////////////////////
static RGBQUAD RGBtoRGBQUAD(COLORREF cr)
{
    RGBQUAD c;
    c.rgbRed = GetRValue(cr); /* get R, G, and B out of DWORD */
    c.rgbGreen = GetGValue(cr);
    c.rgbBlue = GetBValue(cr);
    c.rgbReserved = GetAValue(cr);
    return c;
}
////////////////////////////////////////////////////////////////////////////////
static COLORREF RGBQUADtoRGB(RGBQUAD c)
{
    return RGBA(c.rgbRed, c.rgbGreen, c.rgbBlue, c.rgbReserved);
}

////////////////////////////////////////////////////////////////////////////////
static float HueToRGB(float n1, float n2, float hue)
{
    //<F. Livraghi> fixed implementation for HSL2RGB routine
    float rValue;

    if (hue > 360)
        hue = hue - 360;
    else if (hue < 0)
        hue = hue + 360;

    if (hue < 60)
        rValue = n1 + (n2 - n1) * hue / 60.0f;
    else if (hue < 180)
        rValue = n2;
    else if (hue < 240)
        rValue = n1 + (n2 - n1) * (240 - hue) / 60;
    else
        rValue = n1;

    return rValue;
}
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
static RGBQUAD HSLtoRGB(RGBQUAD lHSLColor)
{
    //<F. Livraghi> fixed implementation for HSL2RGB routine
    float h, s, l;
    float m1, m2;
    BYTE r, g, b;

    h = (float)lHSLColor.rgbRed * 360.0f / 255.0f;
    s = (float)lHSLColor.rgbGreen / 255.0f;
    l = (float)lHSLColor.rgbBlue / 255.0f;

    if (l <= 0.5)
        m2 = l * (1 + s);
    else
        m2 = l + s - l * s;

    m1 = 2 * l - m2;

    if (s == 0)
    {
        r = g = b = (BYTE)(l * 255.0f);
    }
    else
    {
        r = (BYTE)(HueToRGB(m1, m2, h + 120) * 255.0f);
        g = (BYTE)(HueToRGB(m1, m2, h) * 255.0f);
        b = (BYTE)(HueToRGB(m1, m2, h - 120) * 255.0f);
    }

    RGBQUAD rgb = { b, g, r, 0 };
    return rgb;
}

static void ColorizeMode(BYTE *pArgb, const COLORIZEPARAM &param)
{
    BYTE blue = pArgb[0], green = pArgb[1], red = pArgb[2], alpha = pArgb[3];

    if (alpha == 0)
        return;
    if (alpha != 255)
    {
        red = (red * 255) / alpha;
        green = (green * 255) / alpha;
        blue = (blue * 255) / alpha;
    }
    RGBQUAD pixel = { blue, green, red };

    if (param.a0 == 256)
    {
        RGBQUAD color = RGBtoHSL(pixel);
        color.rgbRed = param.hue;
        color.rgbGreen = param.sat;
        pixel = HSLtoRGB(color);
        red = pixel.rgbRed;
        green = pixel.rgbGreen;
        blue = pixel.rgbBlue;
    }
    else
    {
        RGBQUAD color = pixel;
        RGBQUAD hsl;
        hsl.rgbRed = param.hue;
        hsl.rgbGreen = param.sat;
        hsl.rgbBlue = (BYTE)RGB2GRAY(color.rgbRed, color.rgbGreen, color.rgbBlue);
        hsl = HSLtoRGB(hsl);
        red = (BYTE)((hsl.rgbRed * param.a0 + color.rgbRed * param.a1) >> 8);
        blue = (BYTE)((hsl.rgbBlue * param.a0 + color.rgbBlue * param.a1) >> 8);
        green = (BYTE)((hsl.rgbGreen * param.a0 + color.rgbGreen * param.a1) >> 8);
    }
    if (alpha != 255)
    {
        red = (red * alpha) / 255;
        green = (green * alpha) / 255;
        blue = (blue * alpha) / 255;
    }
    pArgb[0] = blue;
    pArgb[1] = green;
    pArgb[2] = red;
}

bool SDIBHelper::Colorize(IBitmapS *pBmp, COLORREF crRef)
{
    RGBQUAD color = RGBtoRGBQUAD(crRef);
    RGBQUAD hsl = RGBtoHSL(color);
    COLORIZEPARAM param;
    float fBlend = 0.8f;
    BYTE byAlpha = GetAValue(crRef);
    if (byAlpha != 0)
        fBlend = byAlpha * 1.0f / 255;

    FillColorizeParam(param, hsl.rgbRed, hsl.rgbGreen, fBlend);

    DIBINFO di = { (LPBYTE)pBmp->LockPixelBits(), pBmp->Width(), pBmp->Height() };

    bool bRet = ColorTransform(&di, ColorizeMode, param);

    pBmp->UnlockPixelBits(di.pBits);
    return bRet;
}

bool SDIBHelper::Colorize(COLORREF &crTarget, COLORREF crRef)
{
    RGBQUAD color = RGBtoRGBQUAD(crRef);
    RGBQUAD hsl = RGBtoHSL(color);
    COLORIZEPARAM param;

    float fBlend = 0.8f;
    BYTE byAlpha = GetAValue(crRef);
    if (byAlpha != 0)
        fBlend = byAlpha * 1.0f / 255;

    FillColorizeParam(param, hsl.rgbRed, hsl.rgbGreen, fBlend);

    RGBQUAD argbTarget = RGBtoRGBQUAD(crTarget);
    ColorizeMode((BYTE *)&argbTarget, param);
    crTarget = RGBQUADtoRGB(argbTarget);
    return true;
}

bool SDIBHelper::GrayImage(IBitmapS *pBmp)
{
    DIBINFO di = { (LPBYTE)pBmp->LockPixelBits(), pBmp->Width(), pBmp->Height() };
    bool bRet = ColorTransform(&di, GrayMode, 0);
    pBmp->UnlockPixelBits(di.pBits);
    return bRet;
}

static COLORREF CalcAvarageRectColor(const DIBINFO &di, RECT rc)
{
    LPBYTE pLine = di.pBits + di.nWid * rc.top * 4;
    if (rc.right > (int)di.nWid)
        rc.right = di.nWid;
    if (rc.bottom > (int)di.nHei)
        rc.bottom = di.nHei;
    int nWid = rc.right - rc.left;
    int nHei = rc.bottom - rc.top;

    int r = 0, g = 0, b = 0;
    for (int y = 0; y < nHei; y++)
    {
        LPBYTE p = pLine + rc.left * 4;
        for (int x = 0; x < nWid; x++)
        {
            b += *p++;
            g += *p++;
            r += *p++;
            p++; // skip alpha
        }
        pLine += di.nWid * 4;
    }
    int nPixels = (nWid * nHei);
    r /= nPixels;
    g /= nPixels;
    b /= nPixels;
    return RGB(r, g, b);
}

static int RgbCmp(const void *p1, const void *p2)
{
    const BYTE *cr1 = (const BYTE *)p1;
    const BYTE *cr2 = (const BYTE *)p2;
    int deltaR = ((int)cr2[0] - (int)cr1[0]);
    int deltaG = ((int)cr2[1] - (int)cr1[1]);
    int deltaB = ((int)cr2[2] - (int)cr1[2]);
    return deltaR + deltaG + deltaB;
}

COLORREF SDIBHelper::CalcAvarageColor(IBitmapS *pBmp, int nPercent, int nBlockSize /*=5*/)
{
    DIBINFO di = { (LPBYTE)pBmp->LockPixelBits(), pBmp->Width(), pBmp->Height() };

    int xBlocks = (di.nWid + nBlockSize - 1) / nBlockSize;
    int yBlocks = (di.nHei + nBlockSize - 1) / nBlockSize;

    int nBlocks = xBlocks * yBlocks;
    COLORREF *pAvgColors = new COLORREF[nBlocks];

    CRect rcBlock(0, 0, nBlockSize, nBlockSize);
    int iBlock = 0;
    for (int y = 0; y < yBlocks; y++)
    {
        for (int x = 0; x < xBlocks; x++)
        {
            pAvgColors[iBlock++] = CalcAvarageRectColor(di, rcBlock);
            rcBlock.OffsetRect(nBlockSize, 0);
        }
        rcBlock.MoveToX(0);
        rcBlock.OffsetRect(0, nBlockSize);
    }
    // RGB排序
    qsort(pAvgColors, nBlocks, sizeof(COLORREF), RgbCmp);

    int nThrows = nBlocks * (100 - nPercent) / 200; //一端丢弃数量
    int iBegin = nThrows;
    int iEnd = nBlocks - nThrows;

    int r = 0, g = 0, b = 0;
    for (int i = iBegin; i < iEnd; i++)
    {
        BYTE *p = (BYTE *)(pAvgColors + i);
        r += p[0];
        g += p[1];
        b += p[2];
    }

    r /= (iEnd - iBegin);
    g /= (iEnd - iBegin);
    b /= (iEnd - iBegin);

    delete[] pAvgColors;
    pBmp->UnlockPixelBits(di.pBits);

    return RGB(r, g, b);
}

SNSEND